﻿using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using Sigil.Impl;

namespace Sigil
{
	public partial class Emit<DelegateType>
	{
		/// <summary>
		/// Pops a value, an index, and a reference to an array off the stack.  Places the given value into the given array at the given index.
		/// </summary>
		public Emit<DelegateType> StoreElement<ElementType>()
		{
			return StoreElement(typeof(ElementType));
		}

		/// <summary>
		/// Pops a value, an index, and a reference to an array off the stack.  Places the given value into the given array at the given index.
		/// </summary>
		public Emit<DelegateType> StoreElement(Type elementType)
		{
			if (elementType == null)
			{
				throw new ArgumentNullException("elementType");
			}

			var arrayType = elementType.MakeArrayType();

			IEnumerable<StackTransition> transitions = null;

			OpCode? instr = null;

			if (elementType.IsPointer)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { elementType, typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { elementType, typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				instr = OpCodes.Stelem_I;
			}

			if (!elementType.IsValueType && !instr.HasValue)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { elementType, typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { elementType, typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				instr = OpCodes.Stelem_Ref;
			}

			if ((elementType == typeof(sbyte) || elementType == typeof(Byte)) && !instr.HasValue)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { typeof(Int32), typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { typeof(Int32), typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				instr = OpCodes.Stelem_I1;
			}

			if ((elementType == typeof(Int16) || elementType == typeof(UInt16)) && !instr.HasValue)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { typeof(Int32), typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { typeof(Int32), typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				instr = OpCodes.Stelem_I2;
			}

			if ((elementType == typeof(Int32) || elementType == typeof(UInt32)) && !instr.HasValue)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { typeof(Int32), typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { typeof(Int32), typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				instr = OpCodes.Stelem_I4;
			}

			if ((elementType == typeof(Int64) || elementType == typeof(UInt64)) && !instr.HasValue)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { typeof(Int64), typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { typeof(Int64), typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				instr = OpCodes.Stelem_I8;
			}

			if (elementType == typeof(Single) && !instr.HasValue)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { typeof(Single), typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { typeof(Single), typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				instr = OpCodes.Stelem_R4;
			}

			if (elementType == typeof(Double) && !instr.HasValue)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { typeof(Double), typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { typeof(Double), typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				instr = OpCodes.Stelem_R8;
			}

			if (!instr.HasValue)
			{
				transitions =
						new[] {
                        new StackTransition(new [] { elementType, typeof(NativeIntType), arrayType }, Type.EmptyTypes),
                        new StackTransition(new [] { elementType, typeof(Int32), arrayType }, Type.EmptyTypes)
                    };

				UpdateState(OpCodes.Stelem, elementType, Wrap(transitions, "StoreElement"));
				return this;
			}

			UpdateState(instr.Value, Wrap(transitions, "StoreElement"));

			return this;
		}
	}
}